<?php
/**
 *  Generic Default Model: a class for item-based model management:
 *      - the data source is the DB
 *      - the record properties are taken from Module config
 *  Similar to Active Record pattern, in which the record is implemented in Item,
 *      and the data access in Action.
 *
 *  @todo select item externally ? or leave it up to subclass only
 *
 */
namespace Yz\modules\Generic;

class Model_Default
    extends Model_Abstract
    implements \Yz_Model_Interface
{


    /************************************
     *          Public methods          *
     ************************************/


    /**
     *
     *  @param int $id
     *  @return Yz_Item_Interface
     *  @throws NotFound
     */
    public function fetchItemById($id)
    {
        debug();
        return $this->_fetchItemWithAction(
                null,
                'SelectById',
                function($action) use ($id)
                {
                    $action->setId($id);
                    return $action();
                }
            )
        ;
    }

    /**
     *
     *  @param Yz_Item_Interface $item
     *  @return Yz_Item_Interface
     */
    public function fetchPreviousItem(\Yz_Item_Interface $item)
    {
        debug();
        $id = $item->id;
        return $this->_fetchItemWithAction(
                null,
                'SelectNeighbor',
                function($action) use ($id)
                {
                    $action
                        ->selectPreviousItem()
                        ->setId($id);
                    return $action();
                }
            )
        ;

        ///////////
        $item0 = $this->selectItem();
        $action = $this->selectAction('SelectNeighbor');
        $action
            ->assignFields($item0->describe())
            ->setId($item->id)
            ->selectPreviousItem()
            ;

        $found = $action->execute()->count();
        if (0 === $found) {
            return null;
        } else {
            $values = $action->fetch();

            return $item0->assignValues($values);
        }
    }

    /**
     *
     *  @param Yz_Item_Interface $item
     *  @return Yz_Item_Interface
     */
    public function fetchNextItem(\Yz_Item_Interface $item)
    {
        debug();
        $id = $item->id;
        return $this->_fetchItemWithAction(
                null,
                'SelectNeighbor',
                function($action) use ($id)
                {
                    $action
                        ->selectNextItem()
                        ->setId($id);
                    return $action();
                }
            )
        ;

        ///////////

        $item0 = $this->selectItem();
        $action = $this->selectAction('SelectNeighbor');
        $action
            ->assignFields($item0->describe())
            ->setId($item->id)
            ->selectNextItem()
            ;

        $found = $action->execute()->count();
        if (0 === $found) {
            return null;
        } else {
            $values = $action->fetch();

            return $item0->assignValues($values);
        }
    }


    /**
     *  updateItemById
     *
     *  @return chainable
     */
    public function updateItemById(\Yz_Item_Interface $item)
    {
        debug();
        $action = $this->selectAction('UpdateById');

        $action
            ->setId($item->id)
            ->assignFields($item->describe())
            ->assignValues($item)
            ;
        $action(); // returns action (ignored) or throws exception
        return $this;
    }

    /**
     *
     *  @return int insert-id/sequence#
     */
    public function createItem()
    {
        debug();
        $action = $this->selectAction('Insert');
        return $action()->getInsertId();
    }

    /**
     *
     *  @return int insert-id/sequence#
     */
    public function addItem(\Yz_Item_Interface $item)
    {
        debug();
        $action = $this->selectAction('Insert');
        $action
            ->assignFields($item->describe())
            ->assignValues($item);
        $newId = $action()->getInsertId();
        $item->id = $newId;
        return $this;
    }

    /**
     *
     *  @return chainable
     */
    public function deleteItemById(\Yz_Item_Interface $item)
    {
        debug();
        $action = $this->selectAction('DeleteById');
        $action->setId($item->id);
        $action(); // returns action (ignored) or throws exception

        return $this;
    }

    /************************************
     *       Protected methods          *
     ************************************/



    /** execute a (fetching) action with item as descriptor/template and a callback applied to action
     *      and return a new item populated with gotten values
     *  @param string $itemName
     *  @param string $actionName
     *  @param callback $apply: function(Yz_Action action) : action
     *  @return Yz_Item_Interface
     *  @throw NotFound/SQL/...
     */
    protected function _fetchItemWithAction( $itemName, $actionName, $apply )
    {
        debug();
        $item = $this->selectItem($itemName);

        $action = $this
            ->selectAction($actionName)
            ->assignFields($item->describe());

        $values = $apply( $action )->fetch();

        return $item->assignValues($values);
    }


}
